//
// MethodFactory.cpp
// Factory for the supported iterative methods.
//
// Author: Gabriel H. Barbuto (gbarbuto@dc.uba.ar)
//
#include <functional>
#include <map>
#include <memory>
#include <string>

#include "BisectionMethod.h"
#include "IterativeMethod.h"
#include "MethodFactory.h"
#include "NewtonMethod.h"
#include "SecantMethod.h"

MethodFactory::MethodFactory()
{
	this->initialize_methods();
}

void MethodFactory::initialize_methods()
{
	this->_methods[BISECTION_METHOD_NAME][DIRECT_FUNCTION_NAME] = [](double const &alpha) {
		return new BisectionMethod([&alpha](double const &x) { return x * x - alpha; });
	};

	this->_methods[NEWTON_METHOD_NAME][DIRECT_FUNCTION_NAME] = [](double const &alpha) {
		return new NewtonMethod([&alpha](double const &x) { return x * x - alpha; },
								[](double const &x) { return 2.0 * x; });
	};

	this->_methods[SECANT_METHOD_NAME][DIRECT_FUNCTION_NAME] = [](double const &alpha) {
		return new SecantMethod([&alpha](double const &x) { return x * x - alpha; });
	};

	this->_methods[BISECTION_METHOD_NAME][INVERSE_FUNCTION_NAME] = [](double const &alpha) {
		return new BisectionMethod([&alpha](double const &x) { return 1.0 / (x * x) - alpha; });
	};

	this->_methods[NEWTON_METHOD_NAME][INVERSE_FUNCTION_NAME] = [](double const &alpha) {
		return new NewtonMethod([&alpha](double const &x) { return 1.0 / (x * x) - alpha; },
								[](double const &x) { return -2.0 / (x * x * x); });
	};

	this->_methods[SECANT_METHOD_NAME][INVERSE_FUNCTION_NAME] = [](double const &alpha) {
		return new SecantMethod([&alpha](double const &x) { return 1.0 / (x * x) - alpha; });
	};
}

IterativeMethod *MethodFactory::get(std::string const &function_name, std::string const &method_name, double const &alpha) const
{
	return (this->_methods.at(method_name).at(function_name))(alpha);
}

// Define class variable.
std::unique_ptr< MethodFactory > MethodFactory::_unique_instance;

// Define class constants and initialize them.
std::string const MethodFactory::DIRECT_FUNCTION_NAME("direct");
std::string const MethodFactory::INVERSE_FUNCTION_NAME("inverse");

std::string const MethodFactory::BISECTION_METHOD_NAME("bisection");
std::string const MethodFactory::NEWTON_METHOD_NAME("newton");
std::string const MethodFactory::SECANT_METHOD_NAME("secant");
